#pragma once

/// logging
#ifdef DEBUG
#define MYLOG(...) fprintf(stdout, __VA_ARGS__);
#else
#define MYLOG(...)
#endif

/// memory
#define H2D cudaMemcpyHostToDevice
#define D2H cudaMemcpyDeviceToHost
#define D2D cudaMemcpyDeviceToDevice

#define TOHOST(pdev, phost, num) cudaMemcpy((phost), (pdev), sizeof(*(pdev))*(num), D2H)
#define TODEV(pdev, phost, num) cudaMemcpy((pdev), (phost), sizeof(*(phost))*(num), H2D)
#define CLEAN(pdev, num) cudaMemset((pdev), 0, sizeof(*(pdev))*num)

/// cuda error check
#define CudaCheckError() __CudaCheckError(__FILE__, __LINE__)

static void 
__CudaCheckError(const char* file, const int line){
  cudaError_t err = cudaGetLastError();
  if(cudaSuccess != err){
    fprintf(stderr, "cudaCHeckError() failed at %s:%i : %s\n", file, line, cudaGetErrorString(err));
    exit(-1);
  }
}

// HandleError
static void 
HandleError( cudaError_t err, const char *file, int line ) {
  if (err != cudaSuccess) {
    printf( "%s in %s at line %d\n", \
    cudaGetErrorString( err ), file, line );
    exit( EXIT_FAILURE );
  }
}

#define H_ERR( err ) (HandleError( err, __FILE__, __LINE__ ))

#define ASSERT( Predicate, Err_msg ) \
if(true){                            \
  if( !(Predicate) ){                \
    std::cerr << "CHECK failed :"    \
      << Err_msg  << " at ("         \
      << __FILE__ << ", "            \
      << __LINE__ << ")"             \
      << std::endl;                  \
    exit(1);                         \
  }                                  \
}

#define CUDA_SOLE_KERNEL_CHECK(...)    \
  do {                                 \
    cudaError_t launch_err, async_err; \
    __VA_ARGS__;                       \
    launch_err = cudaGetLastError();   \
    if(launch_err != cudaSuccess)      \
       fprintf(stderr, "kernel launch %s failed at %s:%i : %s\n",   \
               #__VA_ARGS__, __FILE__, __LINE__, cudaGetErrorString(launch_err)); \
    async_err = cudaDeviceSynchronize();                            \
    if(async_err != cudaSuccess)                                    \
       fprintf(stderr, "kernel exec %s failed at %s:%i : %s\n",     \
               #__VA_ARGS__, __FILE__, __LINE__, cudaGetErrorString(async_err)); \
  } while (0)

#define CUDA_LAUNCH_CHECK(...)         \
  do {                                 \
    cudaError launch_err;              \
    __VA_ARGS__;                       \
    launch_err = cudaGetLastError();   \
    if(launch_err != cudaSuccess)      \
      fprintf(stderr, "kernel launch %s failed at %s:%i : %s\n",   \
              #__VA_ARGS__, __FILE__, __LINE__, cudaGetErrorString(launch_err)); \
  } while (0)

/// pytorch error check
#define CHECK_INPUT(x) AT_ASSERTM(x, "Input mismatch")
#define CHECK_CUDA(x) AT_ASSERTM(x.device().is_cuda(), #x " must be CUDA tensor")


/// param dispatcher
#define __SWITCH_beg(var, lit, sym, ...)\
if (var == lit) { constexpr int sym = lit; {__VA_ARGS__} }

#define __SWITCH(var, lit, sym, ...)\
else if (var == lit) { constexpr int sym = lit; {__VA_ARGS__} }

#define SWITCH_32(var, sym, ...) do {\
__SWITCH_beg(var, 1, sym, __VA_ARGS__)\
__SWITCH(var, 2, sym, __VA_ARGS__)\
__SWITCH(var, 4, sym, __VA_ARGS__)\
__SWITCH(var, 8, sym, __VA_ARGS__)\
__SWITCH(var, 16, sym, __VA_ARGS__)\
__SWITCH(var, 32, sym, __VA_ARGS__)\
} while(0)

#define SWITCH_8(var, sym, ...) do {\
__SWITCH_beg(var, 1, sym, __VA_ARGS__)\
__SWITCH(var, 2, sym, __VA_ARGS__)\
__SWITCH(var, 4, sym, __VA_ARGS__)\
__SWITCH(var, 8, sym, __VA_ARGS__)\
} while(0)

#define SWITCH_4(var, sym, ...) do {\
__SWITCH_beg(var, 1, sym, __VA_ARGS__)\
__SWITCH(var, 2, sym, __VA_ARGS__)\
__SWITCH(var, 4, sym, __VA_ARGS__)\
} while(0)
